""" Full assembly of the parts to form the complete network """

from .unet_parts import *


class UNet(nn.Module):
    def __init__(self, n_channels, n_classes, device,bilinear=False,classification =True):
        super(UNet, self).__init__()
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.bilinear = bilinear
        self.classification = classification
        self.device = device

        self.inc = DoubleConv(n_channels, 64)
        self.down1 = Down(64, 128)
        self.down2 = Down(128, 256)
        self.down3 = Down(256, 512)
        factor = 2 if bilinear else 1
        self.down4 = Down(512, 1024 // factor)
        self.up1 = Up(1024, 512 // factor, bilinear)
        self.up2 = Up(512, 256 // factor, bilinear)
        self.up3 = Up(256, 128 // factor, bilinear)
        self.up4 = Up(128, 64, bilinear)
        self.outc = OutConv(64, n_classes)

        if self.classification:
            self.gap = GAP()
            self.cls_out = OutCls((1024+512+256+128+64)*3,3)

        # Multi-task learning sigma
        self.sigma_seg = nn.Parameter(torch.tensor(0.0, device=self.device, dtype=torch.float32))
        self.sigma_cls = nn.Parameter(torch.tensor(0.0, device=self.device, dtype=torch.float32))

    def forward(self, images): 


        x1 = self.inc(images)
        x2 = self.down1(x1)
        x3 = self.down2(x2)
        x4 = self.down3(x3)
        x5 = self.down4(x4)
        x_up1 = self.up1(x5, x4)
        x_up2 = self.up2(x_up1, x3)
        x_up3 = self.up3(x_up2, x2)
        x = self.up4(x_up3, x1)
        logits = self.outc(x)



        return logits


    def calculate_MTL_loss(self,cls_loss,seg_loss):
        cls_sigma = torch.exp(self.sigma_cls)
        loss_weighted_cls = cls_loss/cls_sigma/cls_sigma+torch.log(cls_sigma+1.0)        
        seg_sigma = torch.exp(self.sigma_seg)
        loss_weighted_seg = seg_loss/seg_sigma/seg_sigma+torch.log(seg_sigma+1.0)  

        return loss_weighted_cls+loss_weighted_seg